/*
 *  stk3a5x_tuning.c - Linux kernel modules for sensortek stk3a5x,
 *  proximity/ambient light sensor (Algorithm)
 *
 *  Copyright (C) 2012~2019 Taka Chiu, sensortek Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "stk3a5x.h"

#ifdef STK_TUNE0
void stk3a5x_ps_smudge_judgement(struct stk3a5x_data *alps_data, uint16_t ps_raw_data)
{
    uint16_t raw_data = 0.0;

    if (ps_raw_data > alps_data->ps_info.psi)
    {
        raw_data = ps_raw_data - alps_data->ps_info.psi;
        APS_LOG("raw_data = %d\n", raw_data);
    }

    if ((raw_data > STK3A5X_SMUDGE_DIFF) && (alps_data->ps_info.smudge_update == false) && (alps_data->ps_info.psi != 0xFFFF))
    {
        alps_data->ps_info.thd_near = alps_data->ps_info.psi + STK3A5X_SMUDGE_NT;
        alps_data->ps_info.thd_far  = alps_data->ps_info.psi + STK3A5X_SMUDGE_FT;
        stk3a5x_set_ps_thd(alps_data, alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
        alps_data->ps_info.smudge_update = true;
        APS_LOG("near = 0x%X, far = 0x%X\n", alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
    }
}

static void stk3a5x_ps_tune_zero_ps_enable(struct stk3a5x_data *alps_data, bool enable)
{
    int32_t err;
    uint8_t reg_value;
    bool config_changed = false;
    err = STK3A5X_REG_READ(alps_data, STK3A5X_STATE_REG);

    if (err < 0)
    {
        APS_ERR("read i2c (0x%X) error\n", STK3A5X_STATE_REG);
        return;
    }

    reg_value = (uint8_t)err;
    reg_value &= (~(STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK | STK3A5X_STATE_EN_WAIT_MASK));

    if (enable)
    {
        if (alps_data->ps_info.enable
#ifdef STK_PS_CALI
            || alps_data->ps_info.cali_enable
#endif
           )
        {
            APS_LOG("PS is running\n");
        }
        else
        {
            if (alps_data->als_info.enable
#ifdef STK_ALS_CALI
                || alps_data->als_info.cali_enable
#endif
               )
            {
                reg_value |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_ALS_MASK);
            }
            else
            {
                reg_value |= (STK3A5X_STATE_EN_PS_MASK | STK3A5X_STATE_EN_WAIT_MASK);
            }

            config_changed = true;
        }
    }
    else
    {
        if (alps_data->ps_info.enable
#ifdef STK_PS_CALI
            || alps_data->ps_info.cali_enable
#endif
           )
        {
            APS_LOG("Other thread used, ignore!\n");
        }
        else
        {
            if (alps_data->als_info.enable
#ifdef STK_ALS_CALI
                || alps_data->als_info.cali_enable
#endif
               )
            {
                reg_value |= STK3A5X_STATE_EN_ALS_MASK;
            }

            config_changed = true;
        }
    }

    if (config_changed)
    {
        err = STK3A5X_REG_READ_MODIFY_WRITE(alps_data, STK3A5X_STATE_REG, reg_value, 0xFF);

        if (err < 0)
        {
            APS_ERR("read modify write i2c (0x%X) error\n", STK3A5X_STATE_REG);
            return;
        }
    }

    alps_data->ps_info.boot_enable = enable;
}

static void stk3a5x_ps_tune_zero_reset(struct stk3a5x_data *alps_data)
{
    memset(alps_data->ps_info.ps_stat_data, 0, sizeof(uint32_t) * (STK_PS_CALI_DATA_NUM - 1));
    alps_data->ps_info.ps_stat_data[2] = 9999;
    alps_data->ps_info.data_count = 0;
}

static int stk3a5x_ps_tune_zero_final(struct stk3a5x_data *alps_data)
{
    int32_t diff_data;

    if (alps_data->ps_info.data_count == -1)
    {
        APS_LOG("exceed limit\n");
        alps_data->ps_info.tune_zero_init_proc = false;
        alps_data->ps_info.tune0_done          = true;
        stk3a5x_ps_tune_zero_reset(alps_data);
        return 0;
    }

    alps_data->ps_info.tune_zero_init_proc = false;
    alps_data->ps_info.tune0_done          = true;
    alps_data->ps_info.psa = alps_data->ps_info.ps_stat_data[0];
    alps_data->ps_info.psi = alps_data->ps_info.ps_stat_data[2];
    diff_data = (int32_t)alps_data->ps_info.ps_stat_data[2] - (int32_t)alps_data->ps_info.thd_near;

    if ((alps_data->ps_info.ps_stat_data[2] > alps_data->ps_info.thd_near) && (STK_ABS(diff_data) > STK3A5X_MAX_MIN_DIFF))
    {
        APS_LOG("Exceed automatic adjust range\n");
    }
    else
    {
        alps_data->ps_info.thd_near = alps_data->ps_info.ps_stat_data[2] + alps_data->ps_info.ht_n_ct;
        alps_data->ps_info.thd_far  = alps_data->ps_info.ps_stat_data[2] + alps_data->ps_info.lt_n_ct;
        stk3a5x_set_ps_thd(alps_data, alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
    }

    stk3a5x_ps_tune_zero_reset(alps_data);
    return 0;
}

static void stk3a5x_ps_prx_tune_zero_threshold_reset(struct stk3a5x_data *alps_data, uint16_t ps_raw_data)
{
    if (ps_raw_data > alps_data->ps_info.ps_stat_data[0])
    {
        alps_data->ps_info.ps_stat_data[0] = ps_raw_data;
    }

    if (ps_raw_data < alps_data->ps_info.ps_stat_data[2])
    {
        alps_data->ps_info.ps_stat_data[2] = ps_raw_data;
    }
}

static int32_t stk3a5x_ps_tune_zero_get_ps_data(struct stk3a5x_data *alps_data)
{
    uint16_t ps_adc;
    int ret;
    ret = stk3a5x_ps_val(alps_data);

    if (ret < 0)
    {
        APS_ERR("fail, ret=%d\n", ret);
        return ret;
    }
    else if (ret == 0x7FFF0001)
    {
        alps_data->ps_info.data_count = -1;
        stk3a5x_ps_tune_zero_final(alps_data);
        return 0;
    }

    ret = stk3a5x_ps_get_data_without_notify(alps_data, &ps_adc);

    if (ret < 0)
    {
        APS_ERR("get data fail, ret=%d\n", ret);
        return ret;
    }
    else if (ret == STK3A5X_PS_DATA_UNAVAILABLE)
    {
        APS_ERR("get data is not ready (Wait next data)\n");
        return 0;
    }

    APS_LOG("ps_adc #%d=%d\n", alps_data->ps_info.data_count, ps_adc);
    alps_data->ps_info.ps_stat_data[1] += ps_adc;
    stk3a5x_ps_prx_tune_zero_threshold_reset(alps_data, ps_adc);
    alps_data->ps_info.data_count++;

    if (alps_data->ps_info.data_count == 2)
    {
        alps_data->ps_info.ps_stat_data[1] /= alps_data->ps_info.data_count;
        stk3a5x_ps_tune_zero_final(alps_data);
    }

    return 0;
}

static void stk3a5x_ps_tune0_zero_tracking_recali(struct stk3a5x_data *alps_data, uint16_t word_data)
{
    uint16_t ct_value;

    if (alps_data->ps_info.last_nearby == STK3A5X_PRX_FAR_AWAY)
    {
        alps_data->ps_info.ps_stat_data[1] += word_data;
        stk3a5x_ps_prx_tune_zero_threshold_reset(alps_data, word_data);
        alps_data->ps_info.data_count ++;
        APS_LOG("count = %d, word_data = %d\n", alps_data->ps_info.data_count, word_data);

        if (alps_data->ps_info.data_count == STK3A5X_TRACKING_QUANTI)
        {
            alps_data->ps_info.ps_stat_data[1] /= alps_data->ps_info.data_count;
            ct_value = alps_data->ps_info.thd_near - alps_data->ps_info.ht_n_ct;
            APS_LOG("ct_value = %d, word_data = %d\n", ct_value, word_data);

            if ((alps_data->ps_info.ps_stat_data[1] < ct_value) &&
                ((ct_value - alps_data->ps_info.ps_stat_data[1]) >= 5) &&
                ((alps_data->ps_info.ps_stat_data[0] - alps_data->ps_info.ps_stat_data[2]) <= STK3A5X_QUANTI_RANGE))
            {
                APS_LOG("%s: ps variation = %d\n",__func__, (alps_data->ps_info.ps_stat_data[0] - alps_data->ps_info.ps_stat_data[2]));
                alps_data->ps_info.thd_near = alps_data->ps_info.ps_stat_data[1] + alps_data->ps_info.ht_n_ct;
                alps_data->ps_info.thd_far  = alps_data->ps_info.ps_stat_data[1] + alps_data->ps_info.lt_n_ct;
                alps_data->ps_info.psi      = alps_data->ps_info.ps_stat_data[1];
                stk3a5x_set_ps_thd(alps_data, alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
                alps_data->ps_info.smudge_update = false;
            }

            stk3a5x_ps_tune_zero_reset(alps_data);
        }
    }
    else
    {
        stk3a5x_ps_tune_zero_reset(alps_data);
        stk3a5x_ps_smudge_judgement(alps_data, word_data);
    }
}

static void stk3a5x_ps_tune_zero_tracking_max_min(struct stk3a5x_data *alps_data, uint16_t word_data)
{
    if (word_data > alps_data->ps_info.psa)
    {
        alps_data->ps_info.psa = word_data;
    }

    if (word_data < alps_data->ps_info.psi)
    {
        alps_data->ps_info.psi = word_data;
    }
}

static void stk3a5x_ps_tune_zero_tracking_task(struct stk3a5x_data *alps_data)
{
    int ret;
    uint16_t raw_data;
    uint16_t diff;
    ret = stk3a5x_ps_val(alps_data);

    if ((ret < 0) || (ret == STK3A5X_PS_BGIR_INVALID))
    {
        APS_ERR("fail, ret=%d\n", ret);
        return;
    }
    else
    {
        ret = stk3a5x_ps_get_data_without_notify(alps_data, &raw_data);

        if (ret < 0)
        {
            APS_ERR("get data fail, ret=%d\n", ret);
            return;
        }
        else if (ret == STK3A5X_PS_DATA_UNAVAILABLE)
        {
            APS_ERR("get data is not ready (Wait next data)\n");
            return;
        }

        APS_LOG("raw_data = %d\n", raw_data);
    }

    if (alps_data->ps_info.psi_set != 0xFFFF)
    {
        stk3a5x_ps_tune0_zero_tracking_recali(alps_data, raw_data);
    }
    else
    {
        stk3a5x_ps_tune_zero_tracking_max_min(alps_data, raw_data);
        diff = alps_data->ps_info.psa - alps_data->ps_info.psi;

        if (diff > STK3A5X_MAX_MIN_DIFF)
        {
            alps_data->ps_info.psi_set  = alps_data->ps_info.psi;
            alps_data->ps_info.thd_near = alps_data->ps_info.psi + alps_data->ps_info.ht_n_ct;
            alps_data->ps_info.thd_far  = alps_data->ps_info.psi + alps_data->ps_info.lt_n_ct;
            APS_LOG("%s: near_thd = %d, far_thd = %d\n", __func__,alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
            stk3a5x_set_ps_thd(alps_data, alps_data->ps_info.thd_near, alps_data->ps_info.thd_far);
        }
    }
}

void stk3a5x_ps_tune0_timer_thread(struct work_struct *work)
{
    struct stk3a5x_data *alps_data = container_of(work, struct stk3a5x_data, stk_ps_tune0_work);

    if (alps_data->ps_info.tune_zero_init_proc)
    {
        if (!alps_data->ps_info.enable && !alps_data->ps_info.boot_enable)
        {
            stk3a5x_ps_tune_zero_ps_enable(alps_data, true);
        }
        else
        {
            stk3a5x_ps_tune_zero_get_ps_data(alps_data);
        }
    }
    else
    {
        if (alps_data->ps_info.boot_enable)
        {
            stk3a5x_ps_tune_zero_ps_enable(alps_data, false);
        }

        if (alps_data->ps_info.enable)
        {
            stk3a5x_ps_tune_zero_tracking_task(alps_data);
        }
    }

    return;
}
#endif
